package cz.acies.uniql.metadata;

import java.sql.*;
import java.util.*;


import cz.acies.uniql.connect.ConnectorHelper;
import cz.acies.uniql.dialect.Dialect;
import cz.acies.uniql.sql.AlterTable;

/**
 * The Class DBMetaData.
 * 
 * Tr��da je kontejnerem pro MetaData v�ech tabulek v Datab�zi.
 * 
 */
public class DBMetaData extends HashMap<String, Object>
{
	private static final long serialVersionUID = 1491884203505129992L;

	private	Dialect dialect = null;
	private	String catalog = null;
	private	String schema = null;
	
	/**
	 * Konstruktor tr�dy DBMetaData.
	 */
	public DBMetaData(){
	}

	public DBMetaData(Dialect dialect) {
		super();
		this.dialect = dialect;
	}

	public DBMetaData initialize(Connection conn) {
		try {
			List<String> tables = ConnectorHelper.getTableList(conn);
			for (Iterator<String> it = tables.iterator(); it.hasNext();) {
				String tableName = it.next();
				addTable(tableName, conn);				
			}
		} catch (Exception exc) {
			exc.printStackTrace();
		}
		return this;
	}
	
	// -----  -----------------------------------------------------------------
	
	public int updateColumn(String tableName, FieldMetaData fmd, boolean isIndex, String actionType, Connection conn) throws Exception {
		if (fmd != null) {
			TableMetaData tbmd = this.getTable(tableName);
			if (tbmd != null) {
				tbmd.setDBMetaData(this);
				return tbmd.updateStructureColumn(fmd, isIndex, actionType, conn);
			}
		}
		return -1;
	}

	public int updateColumn( 
		String tableName,
		String columnName, 
		int sqlType, 
		int size, 
		int decimal, 
		boolean nullable, 
		String value,
		boolean pk, 
		boolean unique, 
		boolean autoincrement, 
		boolean isIndex, 
		String actionType, 
		Connection conn) 
	throws Exception {
		
		actionType = this.qualifyAlterTableActionType(actionType);
		TableMetaData tbmd = this.getTable(tableName);
		if (tbmd != null) {
			tbmd.setDBMetaData(this);
			FieldMetaData fmd = tbmd.getFieldByName(columnName);
			if (fmd == null && actionType.equals(AlterTable.ADD_COLUMN)) {
				fmd = new FieldMetaData(getDialect(), this);
			} else if (fmd == null) {
				return -1;
			}
			FieldMetaData fmd2 = new FieldMetaData(dialect, this);
			fmd2.set(tableName, columnName, sqlType, size, decimal, nullable, value, pk, unique, autoincrement);
			paramsVerify(sqlType, size, decimal, nullable, value);
			if (fmd2.compare(fmd)) {
				return -1;	
			}
			fmd.set(tableName, columnName, sqlType, size, decimal, nullable, value, pk, unique, autoincrement);
			if (tbmd.getFieldByName(columnName) != null && (actionType.equals(AlterTable.ADD_COLUMN) || actionType.trim().length() == 0)) {
				actionType = AlterTable.CHANGE_COLUMN;
			}
			return updateColumn(tableName, fmd, isIndex, actionType, conn);
		} else {
			throw new Exception("DBMetaData.updateStructureColumn: Bad tableName or table is not contained in DBMetaData... ");
		}
	}
	
	public void renameColumn(String tableName, String columnName, String newColumnName, Connection conn) throws Exception {
		TableMetaData tbmd = this.getTable(tableName);
		if (tbmd != null) {
			if (columnName.equals(newColumnName)) return;
			tbmd.setDBMetaData(this);
			FieldMetaData fmd = tbmd.getFieldByName(columnName);
			if (fmd == null) {
				throw new Exception("DBMetaData.renameColumn: Column name ("+columnName+") in table ("+tableName+") is not contained in DBMetaData... ");
			} else {
				if (tbmd.getFieldByName(newColumnName) != null) return;
				
				String query = "ALTER TABLE "+tableName+" CHANGE COLUMN "+columnName+" "+newColumnName;
				
				Statement stmt = null;
				ResultSet rs = null;
				try {
					stmt = this.getDialect().createStatement(conn);
					stmt.execute(query);
				} catch (Exception exc) {
					exc.printStackTrace();
				} finally {
					try { if (rs != null) rs.close(); } catch (SQLException e) {}
					try { if (stmt != null) stmt.close(); } catch (SQLException e) {}
				} 
					
			}
		} else {
			throw new Exception("DBMetaData.renameColumn: Bad tableName ("+tableName+") or table is not contained in DBMetaData... ");
		}
	}
	
	private void paramsVerify(int sqlType, int size, int decimal, boolean nullable, String value) {
		switch (sqlType) {
			case Types.DOUBLE:
			case Types.FLOAT: {
				if (size < 15 || size > 38) {
					Exception exc = new Exception("Size u sloupce typu DOUBLE musi byt v rozmezi 15 - 38 vcetne");
					//System.err.println(""+exc.getMessage());
					exc.printStackTrace();
				}
				if (decimal < 1 || decimal > size-1) {
					Exception exc = new Exception("Decimal u sloupce typu DOUBLE musi byt v rozmezi 1 - (size-1) vcetne");
					exc.printStackTrace();
				}
				if (!nullable) {
					try {
						if (!cz.acies.utils.StringUtils.isNumeric(value)) {
							Exception exc = new Exception("Pokud je sloupec typu DOUBLE a NOT NULL musi byt vyplnena defauln� hodnota (napr. 0.00) Chyba: value = ("+value+")");
							//System.err.println(""+exc.getMessage());
							exc.printStackTrace();
						}
					} catch (Exception exc) {}
				}
				break;
			}
			case Types.TINYINT:
			case Types.SMALLINT:
			case Types.INTEGER: {
				if (!nullable) {
					try {
						if (!cz.acies.utils.StringUtils.isNumeric(value)) {
							Exception exc = new Exception("Pokud je sloupec typu INTEGER a NOT NULL musi byt vyplnena defauln� hodnota (napr. 0)");
							//System.err.println(""+exc.getMessage());
							exc.printStackTrace();
						}
					} catch (Exception exc) {}
				}
				break;
			}
			case Types.TIMESTAMP: {
				if (!nullable) {
					try {
						if (!cz.acies.utils.DateUtils.isTimestampValid(value)) {
							Exception exc = new Exception("Pokud je sloupec typu TIMESTAMP a NOT NULL musi byt vyplnena defauln� hodnota (napr. 1900-01-01 00:00:00)");
							//System.err.println(""+exc.getMessage());
							exc.printStackTrace();
						}
					} catch (Exception exc) {}
				}
				break;				
			}
			case Types.DATE: {
				if (!nullable) {
					try {
						if (!cz.acies.utils.DateUtils.isDateValid(value)) {
							Exception exc = new Exception("Pokud je sloupec typu DATE a NOT NULL musi byt vyplnena defauln� hodnota (napr. 1900-01-01)");
							//System.err.println(""+exc.getMessage());
							exc.printStackTrace();
						}
					} catch (Exception exc) {}
				}
				break;
			}
		}
	}
	
	public int updateColumn(String taleName, String columnName, int sqlType, int size, int decimal, boolean nullable, String value, boolean isIndex, String typeOperation, Connection conn) throws Exception {
		return updateColumn(taleName, columnName, sqlType, size, decimal, nullable, value, false, false, false, isIndex, typeOperation, conn);
	}

	private String qualifyAlterTableActionType(String type) {
		if (AlterTable.actionSet.contains(type)) return type;
		else if (type.equals("ADD") || type.trim().length() == 0) return AlterTable.ADD_COLUMN;
		else if (type.equals("CHANGE")) return AlterTable.CHANGE_COLUMN;
		else if (type.equals("DROP")) return AlterTable.DROP_COLUMN;
		else return "";
	}

	// -----  -----------------------------------------------------------------
	
	/**
	 * Prida MetaData tabulky.
	 * 
	 * @param table - jmeno tabulky
	 * @param conn - connection do DB 
	 * 
	 * @throws Exception the exception
	 */
	public void addTable(String table, Connection conn) throws Exception {
		//System.out.println("DBMetaData.addTable().table = "+table);
		TableMetaData tbmd = new TableMetaData();
		tbmd.setDialect(getDialect());
		tbmd.parse(table, conn);
		this.put(table.toLowerCase(), tbmd);
	}

	/**
	 * Prida MetaData tabulky.
	 * 
	 * @param tbmd - metadata tabulky
	 * 
	 * @throws Exception the exception
	 */
	public void addTable(TableMetaData tbmd) throws Exception {		
		String table = tbmd.getName();
		this.put(table.toLowerCase(), tbmd);
	}
	
	/**
	 * Navraci MetaData tabulky vybran� podle n�zvu.
	 * 
	 * @param table - jm�no tabulky
	 * 
	 * @return TableMetaData
	 */
	public TableMetaData getTable(String table)  {
		return (TableMetaData) get(table.toLowerCase());
	}

	/**
	 * Format.
	 * 
	 * @return String
	 */
	public String format() throws Exception {
		String s = "----------[ Database: " + " ]----------";
		Object[] as = this.keySet().toArray();
		for (int i = 0; i < as.length; i++) {
			String table = (String)as[i];
			s += ((TableMetaData)getTable(table)).format();
		}
		return s;
	}

	// ----- Getter & Setter --------------------------------------------------
	
	/**
	 * Navraci dialect.
	 * 
	 * @return dialect
	 */
	public Dialect getDialect() {
		return dialect;
	}

	/**
	 * Zadava dialect.
	 * 
	 * @param dialect 
	 */
	public void setDialect(Dialect dialect) {
		this.dialect = dialect;
	}

	/**
	 * Vr�t� hodnotu prom�nn� catalog 
	 * @return
	 */
	public String getCatalog() {
		return catalog;
	}

	/**
	 * Zadava hodnotu prom�nn� catalog 
	 * @param catalog - jm�no catalogu (jm�mo DB).
	 */
	public void setCatalog(String catalog) {
		this.catalog = catalog;
	}

	/**
	 * hodnotu prom�nn� schema
	 * @return
	 */
	public String getSchema() {
		return schema;
	}

	/**
	 * hodnotu prom�nn� schema
	 * @param schema
	 */
	public void setSchema(String schema) {
		this.schema = schema;
	}
}
